// Copyright (C) 2009 Moccu GmbH & Co. KG, Pappelalle 10, 10437 Berlin, Germany

// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in 
// the Software without restriction, including without limitation the rights to 
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
// of the Software, and to permit persons to whom the Software is furnished to 
// do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all 
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


// Author(s):    Andreas Hug
// Version:      1.0
// Date:         06.07.2009


package com.moccu.lib.graphic;

import javax.microedition.lcdui.Image;

public class Transformation 
{
	/**
	 * Scale an image in horizontal and vertical direction, using bilinear interpolation
	 * @param source Image to scale
	 * @param scaleX Horizontal scaling factor (100% = 1.0)
	 * @param scaleY Vertical scaling factor (100% = 1.0)
	 * @return Scaled image
	 */
	public static Image scale(Image source, float scaleX, float scaleY)
	{
		int width = round(source.getWidth() * scaleX);
		int height = round(source.getHeight() * scaleY);
		
		double factorX = (double) width / (double) source.getWidth();
		double factorY = (double) height / (double) source.getHeight();
		
		int[] scaledPixels = new int[width * height];
		int[] originalPixels = new int[source.getWidth() * source.getHeight()];
		
		source.getRGB(originalPixels, 0, source.getWidth(), 0, 0, source.getWidth(), source.getHeight());
		
		for(int y = 0; y < height; y++)
			for(int x = 0; x < width; x++)
			{
				int x1 = floor(x / factorX);
				int y1 = floor(y / factorY);
				
				int x2 = x1 + 1 < source.getWidth() ? x1 + 1 : x1;
				int y2 = y1 + 1 < source.getHeight() ? y1 + 1 : y1;
				
				double weightH = ((float) x / factorX) - (float) x1;
				double weightV = ((float) y / factorY) - (float) y1;
			
				int c1 = originalPixels[y1 * source.getWidth() + x1];
				int c2 = originalPixels[y1 * source.getWidth() + x2];
				int c3 = originalPixels[y2 * source.getWidth() + x1];
				int c4 = originalPixels[y2 * source.getWidth() + x2];

				int color = bilinearInterpolation(c1, c2, c3, c4, 1-weightH, 1-weightV);
				
				scaledPixels[y * width + x] = color;
			}
		
		return Image.createRGBImage(scaledPixels, width, height, true);
	}
	
	
	/**
	 * Interpolate the color of four pixels
	 * @param tl Top left
	 * @param tr Top right
	 * @param bl Bottom left
	 * @param br Bottom right
	 * @param weightX Horizontal weight (1 = 100% left and 0% right, 0 = 0% left and 100% right) 
	 * @param weightY Vertical weight (1 = 100% top and 0% bottom, 0 = 0% top and 100% bottom) 
	 * @return Interpolated color
	 */
	private static int bilinearInterpolation(int tl, int tr, int bl, int br, double weightX, double weightY)
	{
		int[] argbTL, argbTR, argbBL, argbBR;
		argbTL = colorToArgb(tl);
		argbTR = colorToArgb(tr);
		argbBL = colorToArgb(bl);
		argbBR = colorToArgb(br);
		
		int a = floor(argbTL[0] * weightX * weightY + argbTR[0] * (1-weightX) * weightY + argbBL[0] * weightX * (1-weightY) + argbBR[0] * (1-weightX) * (1-weightY));
		int r = floor(argbTL[1] * weightX * weightY + argbTR[1] * (1-weightX) * weightY + argbBL[1] * weightX * (1-weightY) + argbBR[1] * (1-weightX) * (1-weightY));
		int g = floor(argbTL[2] * weightX * weightY + argbTR[2] * (1-weightX) * weightY + argbBL[2] * weightX * (1-weightY) + argbBR[2] * (1-weightX) * (1-weightY));
		int b = floor(argbTL[3] * weightX * weightY + argbTR[3] * (1-weightX) * weightY + argbBL[3] * weightX * (1-weightY) + argbBR[3] * (1-weightX) * (1-weightY));

		return (a << 24) + (r << 16) + (g << 8) + (b);
	}
	

	/**
	 * Convert an integer to an array with four fields representing alpha, red, green and blue
	 * @param color Color [0x00000000 .. 0xffffffff]
	 * @return Array with splitted channels
	 */
	private static int[] colorToArgb(int color)
	{
		int[] argb = new int[4];
		argb[0] = (color >> 24) & 0xff;
		argb[1] = (color >> 16) & 0xff;
		argb[2] = (color >> 8 ) & 0xff;
		argb[3] = (color) & 0xff;
		
		return argb;
	}
	
	
	private static int round(double number)
	{
		return number % 1 < 0.5 ? (int) number : (int) number + 1;
	}
	
	
	private static int floor(double number)
	{
		return (int) number;
	}
	
	
	public static int ceil(double number)
	{
		return number % 1 == 0 ? (int) number : (int) number + 1;
	}
}
